home *** CD-ROM | disk | FTP | other *** search
- page 60,132
-
- ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- ; * *
- ; * PRNDSK.ASM Version 1.00 (B) *
- ; * *
- ; * A utility to redirect output to a printer or comm port to *
- ; * an MS-DOS disk file. *
- ; * *
- ; * Copyright 1987 by David H. Rifkind *
- ; * *
- ; * Version 1.00 2 Mar 87 *
- ; * *
- ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-
-
- MAXSPEC equ 80 ; Maximum length of a filespec
-
-
- ;
- ; Buffer size and flush point definitions
- ;
-
- buffer_size equ 800h ; Size of each device buffer
- buffer_mark equ 400h ; Flush point for all buffers
-
-
- ;
- ; Beginning of program segment
- ;
-
- Code SEGMENT para
- assume cs:Code,ds:Code
-
- ; PSP structure definitions
-
- org 2Ch
- env_seg_ptr label word ; Environment segment pointer
- org 81h
- command_line label byte ; Program command line tail
-
-
- ;
- ; Beginning of COM binary image
- ;
- org 100h
- Start: jmp PrnDsk ; COM entry point vector
-
- program_id db "PRNDSK 1.00",0 ; Program identification
- id_length equ $-program_id
-
-
- ;
- ; Resident data area
- ;
-
- tsr_pid dw ? ; Program ID of TSR part of PRNDSK
- dos_version db ? ; DOS major version number
- reentry_flag db 0 ; Non-zero indicates our stack in use
- under_0Ch_flag db 0 ; DOS service 1 <= AH <= 0Ch in use
- over_0Ch_flag db 0 ; DOS service 0 or > 0Ch in use
- defer_flag db 0 ; Non-zero indicates buffer(s) waiting
- crit_flag db ? ; Non-zero indicates critical error
- ctxt_caller dw ? ; InContext/OutContext return address
-
- user_pid dw ? ; User's (calling program's) PID
- user_ax dw ? ; User's AX (temporary storage)
- user_sp dw ? ; User's SP
- user_ss dw ? ; and SS
- user_cc db ? ; User's break (control-C) check flag
-
- old_int_14h dw ?,? ; Original vectors for INT 14h,
- old_int_17h dw ?,? ; INT 17h,
- old_int_21h dw ?,? ; and INT 21h
- old_int_24h dw ?,? ; User's critical error vector
-
-
- ;
- ; Printer Redirection Block structure definition
- ;
-
- printer_struc STRUC
-
- prb_buf_ptr dw ? ; Pointer to start of buffer area
- prb_buf_size dw ? ; Size of buffer
- prb_buf_mark dw ? ; Buffer flush point
- prb_buf_count dw ? ; Number of bytes currently in buffer
-
- prb_sys_flags db 0 ; System flag byte (see below)
- prb_user_flags db 0 ; User flag byte (see below)
- prb_handle dw ? ; File handle for this device
- prb_status db ? ; Last DOS file status
-
- prb_filespec db MAXSPEC dup (?) ; Redirection file specification
-
- printer_struc ENDS
-
- ; Definitions of bits in prb_sys_flags (prbs_xxxx) and
- ; prb_user_flags (prbu_xxxx)
-
- prbs_active equ 80h ; 1 indicates device being redirected
- prbs_open equ 40h ; 1 indicates redirection file is open
-
- prbu_append equ 80h ; 1 indicates file to be appended
- prbu_transp equ 40h ; 1 indicates transparent redirection
-
- ; Printer Redirection Blocks - one for each redirectable device
-
- lpt1_prb printer_struc <lpt1_buf,buffer_size,buffer_mark>
- lpt2_prb printer_struc <lpt2_buf,buffer_size,buffer_mark>
- lpt3_prb printer_struc <lpt3_buf,buffer_size,buffer_mark>
- com1_prb printer_struc <com1_buf,buffer_size,buffer_mark>
- com2_prb printer_struc <com2_buf,buffer_size,buffer_mark>
-
- ; Table of pointers to PRBs
-
- printer_prbs label word ; Pointers to printer PRBs
- dw offset lpt1_prb
- dw offset lpt2_prb
- dw offset lpt3_prb
- comm_prbs label word ; Pointers to comm port PRBs
- dw offset com1_prb
- dw offset com2_prb
-
- page
-
- ;
- ; InContext switches to the TSR's stack area and data segment.
- ; User registers are saved at fixed locations on the stack (stack_ax,
- ; stack_es, etc.) and can be directly referenced.
- ;
-
- InContext PROC near
-
- mov cs:reentry_flag,0FFh ; Set reentry flag
- pop cs:ctxt_caller ; Save return address
-
- mov cs:user_ax,ax ; (Temporary)
- mov cs:user_sp,sp ; Save user's stack pointer
- mov cs:user_ss,ss ; and segment
- mov ax,cs
- cli
- mov ss,ax ; Use our stack pointer
- mov sp,offset stack_top
- sti
-
- mov ax,cs:user_ax ; Reload AX...
- push ax ; ...and store it again
- push bx ; Save all registers
- push cx
- push dx
- push bp
- push si
- push di
- push ds
- push es
-
- push cs ; Move CS...
- pop ds ; ...to DS
-
- jmp ctxt_caller ; Return to caller
-
- InContext ENDP
-
-
- ;
- ; Switch back to the user's stack and data segment. Registers
- ; are first restored from our stack; some may have been modified.
- ;
-
- OutContext PROC near
-
- pop ctxt_caller ; Save return address
-
- pop es ; Restore user's registers
- pop ds
- pop di
- pop si
- pop bp
- pop dx
- pop cx
- pop bx
- pop ax
-
- cli
- mov ss,cs:user_ss ; Restore user's stack pointer
- mov sp,cs:user_sp
- sti
-
- push cs:ctxt_caller ; Push return address
- mov cs:reentry_flag,0h ; Clear reentrancy flag
- ret
-
- OutContext ENDP
-
-
- ;
- ; Prepare for DOS calls from within the TSR. Sets the break check
- ; flag, critical error vector and current PID.
- ;
-
- InTSR PROC near
-
- mov ax,3300h ; DOS service:
- int 21h ; Get break flag
- mov user_cc,dl ; Save user's break flag
-
- mov dl,0h
- mov ax,3301h ; DOS service:
- int 21h ; Set break flag (0 = no break check)
-
- mov ax,3524h ; DOS service:
- int 21h ; Get INT 24h vector
- mov old_int_24h+0,bx ; Save user's critical error vector
- mov old_int_24h+2,es
-
- mov dx,offset Int_24h ; Point to our critical error handler
- mov ax,2524h ; DOS service:
- int 21h ; Set INT 24h vector
- mov crit_flag,0h ; Clear critical error flag
-
- mov ah,51h ; DOS service:
- int 21h ; Get Process ID
- mov user_pid,bx ; Save user's PID
-
- mov bx,tsr_pid ; Load our Process ID
- mov ah,50h ; DOS service:
- int 21h ; Set PID
-
- ret
-
- InTSR ENDP
-
-
- ;
- ; Restore DOS's state for the user's program. Flags and vectors
- ; changed by InTSR are restored.
- ;
-
- OutTSR PROC near
-
- mov bx,user_pid ; Load user's PID
- mov ah,50h ; DOS service:
- int 21h ; Set Process ID
-
-
- mov dx,old_int_24h+0 ; Point to user's critical error
- mov ax,old_int_24h+2 ; handler
- push ds
- mov ds,ax
- mov ax,2524h ; DOS service:
- int 21h ; Set INT 24h vector
- pop ds
-
- mov dl,user_cc ; Get user's break flag
- mov ax,3301h ; DOS service:
- int 21h ; Set break flag
-
- ret
-
- OutTSR ENDP
-
-
- ;
- ; Critical error (INT 24h) interrupt handler
- ;
- ; Called when a critical error occurs while performing DOS
- ; commands from within the TSR, to prevent unwanted "Retry,
- ; Ignore, Abort?" messages.
- ;
-
- Int_24h PROC far
-
- mov cs:crit_flag,0FFh ; Set critical error flag
-
- mov al,0 ; INT 24h "ignore" flag
- cmp cs:dos_version,3 ; DOS 3.xx or better?
- jb int_24h_exit
- mov al,3 ; Yes - return "fail" flag
- int_24h_exit:
- iret
-
- Int_24h ENDP
-
- page
-
- ;
- ; Flush a redirection buffer. On entry, SI points to the PRB
- ; for an active device. If the associated file is not yet open,
- ; it is opened (this can happen only in one special case).
- ; InTSR should be called BEFORE calling Flush.
- ;
-
- Flush PROC near
-
- test prb_sys_flags[si],prbs_open
- ; Is the file open?
- jnz flush_write ; Yes - go write to file
-
- call FileOpen ; No - open the file
- test prb_status[si],0FFh ; Check for error
- jnz flush_exit
-
- flush_write:
- mov bx,prb_handle[si] ; BX = file handle
- mov cx,prb_buf_count[si] ; CX = number of bytes to write
- mov dx,prb_buf_ptr[si] ; DX = address of buffer
- mov ah,40h ; DOS service:
- int 21h ; Write to file
- jc flush_error
- test crit_flag,0FFh ; Check for critical error
- jnz flush_crit
-
- mov prb_buf_count[si],0 ; Mark buffer as empty
-
- jmp flush_exit
-
- flush_error:
- test crit_flag,0FFh
- jz flush_err_1
- flush_crit:
- mov al,83 ; Fake DOS 3.xx critical error code
- flush_err_1:
- mov prb_status[si],al ; Set file error status byte
- flush_exit:
- ret
-
- Flush ENDP
-
-
- ;
- ; Open a redirection file. On entry, SI points to the PRB
- ; for a device.
- ;
-
- FileOpen PROC near
-
- test prb_user_flags[si],prbu_append
- ; File to be appended to?
- jz file_create ; No - go truncate file
-
- lea dx,prb_filespec[si] ; DX points to filespec
- mov ax,3D01h ; DOS service:
- int 21h ; Open file (access = write)
- jc open_1
- test crit_flag,0FFh ; Check for critical error
- jnz open_crit
- jmp open_done
- open_1: ; Can't open file
- cmp al,2 ; Check for "file not found"
- jnz open_error ; No - it's a real error
-
- file_create:
- lea dx,prb_filespec[si] ; DX points to filespec
- mov cx,0 ; CX = attribute (0 = normal file)
- mov ah,3Ch ; DOS service:
- int 21h ; Create or truncate file
- jc open_error
- test crit_flag,0FFh ; Check for critical error
- jnz open_crit
-
- open_done:
- mov prb_handle[si],ax ; Save file handle
- or prb_sys_flags[si],prbs_open
- ; Flag file as open
-
- mov ax,user_pid ; If the user's PID matches ours,
- cmp ax,tsr_pid ; the file will be closed on exit
- jnz open_2
- and prb_sys_flags[si],not prbs_open
- ; Mark file as closed (!!!)
-
- open_2:
- mov bx,prb_handle[si] ; BX = file handle
- xor cx,cx ; Set offset (CX and DX) to zero
- xor dx,dx
- mov ax,4202h ; DOS service:
- int 21h ; Move file pointer (to EOF)
- jc open_error
- test crit_flag,0FFh ; Check for critical error
- jz open_exit
- jmp open_crit
-
- open_error:
- test crit_flag,0FFh
- jz open_err_1
- open_crit:
- mov al,83 ; Fake DOS 3.xx critical error code
- open_err_1:
- mov prb_status[si],al ; Set file status byte
- open_exit:
- ret
-
- FileOpen ENDP
-
-
- ;
- ; Check the current redirection buffer and see if it needs
- ; to (and can) be flushed. Calls InTSR and OutTSR to set up
- ; the environment for the Flush call.
- ;
- ; Entry: SI points to PRB
- ;
-
- CheckBuf PROC near
-
- test prb_sys_flags[si],prbs_active
- ; Redirection active for this device?
- jz check_exit ; No - skip it
- test prb_status[si],0FFh ; Redirection stopped by error?
- jnz check_exit ; Yes - skip it
-
- mov ax,prb_buf_count[si] ; Check whether buffer is over the
- cmp ax,prb_buf_mark[si] ; trigger point
- jb check_exit ; No - don't flush now
-
- test over_0Ch_flag,0FFh ; Test whether DOS is idle
- jnz cant_flush ; Not idle - can't flush
- cmp dos_version,3 ; DOS version 3 or better...
- jae check_buf_1 ; ...doesn't care about 1 - 12
- test under_0Ch_flag,0FFh
- jnz cant_flush ; Not idle - can't flush
-
- check_buf_1:
- call InTSR ; Set up for DOS access
- call Flush ; Flush the buffer
- call OutTSR ; Put DOS back the way you found it
-
- check_exit:
- ret
-
- cant_flush:
- mov defer_flag,0FFh ; Request for service when DOS
- ; becomes free
- ret
-
- CheckBuf ENDP
-
-
- ;
- ; Add a character to a redirection buffer and call CheckBuf
- ; to try to flush it.
- ;
- ; Entry: AL = character
- ; SI points to PRB
- ;
-
- BufChar PROC near
-
- mov di,prb_buf_count[si] ; Get buffer index
- cmp di,prb_buf_size[si] ; Check for space in buffer
- jnb buf_char_1 ; Full - discard this character
-
- mov bx,prb_buf_ptr[si] ; Pointer to start of buffer
- mov [bx+di],al ; Store character in buffer
- inc di ; Increment buffer count
- mov prb_buf_count[si],di ; and store it
-
- buf_char_1:
- call CheckBuf ; Now try to flush the buffer
-
- ret
-
- BufChar ENDP
-
- page
-
- ;
- ; DOS service (INT 21h) interrupt handler
- ;
- ; This routine replaces the MS-DOS INT 21h service request
- ; handler. It keeps track of entries and exits to make sure
- ; that DOS will not be called reentrantly when flushing a
- ; redirection buffer.
- ;
-
- Int_21h PROC far
- sti
-
- cmp ah,0h ; Service 0h uses second stack
- jz over_0Ch
-
- cmp ah,4Bh ; EXEC requires special treatment
- jz service_4Bh
-
- cmp ah,50h ; Services 50h and 51h use first
- jz under_0Ch ; stack (DOS 2.xx only, but not
- cmp ah,51h ; worth the trouble to handle
- jz under_0Ch ; as a special case)
-
- cmp ah,0Ch ; All other services over 0Ch use
- ja over_0Ch ; second stack
-
- under_0Ch:
- mov cs:under_0Ch_flag,0FFh ; Flag DOS as busy
- pushf
- call dword ptr cs:old_int_21h
- ; Call the old INT 21h handler
- mov cs:under_0Ch_flag,0h ; Clear "DOS busy" flag
- jmp int_21h_1
-
- over_0Ch:
- mov cs:over_0Ch_flag,0FFh ; Flag DOS as busy
- pushf
- call dword ptr cs:old_int_21h
- ; Call the old INT 21h handler
- mov cs:over_0Ch_flag,0h ; Clear the "DOS busy" flag
- jmp int_21h_1
-
- service_4Bh:
- mov cs:over_0Ch_flag,0FFh ; Flag DOS busy
- jmp dword ptr cs:old_int_21h
- ; Jump directly to old INT 21h
-
-
- ; Now see whether any redirection buffers need flushing.
-
- int_21h_1:
- pushf
- test cs:reentry_flag,0FFh ; Check for recursive call
- jnz int_21h_exit
- test cs:defer_flag,0FFh ; Check for deferred service request
- jz int_21h_exit
-
- test cs:under_0Ch_flag,0FFh ; Check whether DOS is really idle
- jnz int_21h_exit
- test cs:over_0Ch_flag,0FFh
- jnz int_21h_exit
-
- call InContext ; Use local stack and registers
-
- mov bx,0 ; Start with buffer for LPT1
- int_21h_loop:
- push bx
- shl bx,1 ; Offset into word table
- mov si,printer_prbs[bx] ; Get address of PRB
- call CheckBuf ; Consider flushing this buffer
- pop bx
- inc bx ; Next PRB...
- cmp bx,5 ; (Highest PRB number is 4)
- jb int_21h_loop
-
- mov defer_flag,0h ; Clear deferred service flag
-
- call OutContext ; Restore user's stack and registers
-
- int_21h_exit:
- popf
- ret 2 ; Return flags to caller
-
- Int_21h ENDP
-
- page
-
- ;
- ; Printer service (INT 17h) interrupt handler
- ;
- ; This replaces the original INT 17h handler (and chains to
- ; it as needed). In addition to redefining the standard printer
- ; services, it adds seven new ones (function codes 80h to 86h)
- ; to control redirection.
- ;
- ; Original services
- ; -------- --------
- ; DX = printer number (0 to 2)
- ;
- ; Entry: AH = 0 to print a character
- ; AL = character to be printed
- ; AH = 1 to initialize printer
- ; AH = 2 to return printer status
- ;
- ; Exit: AH = printer status:
- ; Bit 0 = printer timeout
- ; Bit 3 = I/O error
- ; Bit 4 = device online
- ; Bit 5 = out of paper
- ; Bit 6 = acknowledge
- ; Bit 7 = printer ready
- ;
- ; New Services
- ; --- --------
- ; DX = device number (0 to 4) for services 81h to 85h
- ;
- ; Entry: AH = 80h to return installed state flag
- ; Exit: AX = 5A5Ah
- ; ES points to TSR code segment
- ;
- ; Entry: AH = 81h to start redirecting a device
- ; AL = option flags (prb_user_flags)
- ; DS:SI points to filespec
- ; AH = 82h to terminate redirection and close file
- ; AH = 83h to flush buffer to disk and update file
- ; Exit: AL = DOS error code (zero if no error) (1 byte only!)
- ;
- ; Entry: AH = 84h to return pointer to redirection block
- ; Exit: ES:BX points to printer redirection block
- ;
- ; Entry: AH = 85h to change the option flags
- ; AL = new option flags
- ;
- ; Entry: AH = 86h to return the option flags
- ; Exit: AL = option flags
- ;
-
- Int_17h PROC far
-
- test cs:reentry_flag,0FFh ; Reentrant call (unlikely case)
- jnz jmp_old_17h ; Yes - can't do anything safely
- call InContext ; Switch in our stack and segments
- sti ; Allow hardware interrupts
-
- cmp ah,80h ; New service? (80h <= AH <= 85h)
- jae new_service
-
- cmp dx,2 ; Check printer number 0 to 2
- ja use_old_17h ; Greater than 2 - can't deal with it
- mov bx,dx
- shl bx,1 ; Times two for word offset
- mov si,printer_prbs[bx] ; SI points to PRB
-
- test prb_sys_flags[si],prbs_active
- ; Device being redirected?
- jz use_old_17h ; No - skip it
-
- cmp ah,0
- jz prt_char ; Service 0
- cmp ah,1
- jz prt_init ; Service 1
- cmp ah,2
- jz prt_stat ; Service 2
-
- jmp use_old_17h ; Unknown service number
-
- new_service:
- cmp ah,80h ; Installation check?
- jz install_flag ; Yes - don't check DX
-
- cmp dx,4 ; Device number 0 to 4 (3 & 4 are COM)?
- ja use_old_17h ; No - can't handle it
- mov bx,dx
- shl bx,1
- mov si,printer_prbs[bx] ; SI points to redirection block
-
- cmp ah,81h
- jz set_redir ; Service 81h
- cmp ah,82h
- jz clear_redir ; Service 82h
- cmp ah,83h
- jz flush_redir ; Service 83h
- cmp ah,84h
- jz redir_stat ; Service 84h
- cmp ah,85h
- jz redir_flags ; Service 85h
- cmp ah,86h
- jz return_flags ; Service 86h
-
- use_old_17h:
- mov ax,stack_ax ; Registers changed by our handler
- mov dx,stack_dx
- pushf ; Simulate an INT...
- call dword ptr old_int_17h ; ...to the old INT 17h handler
- mov stack_ax,ax ; Store return value
-
- int_17h_done:
- call OutContext ; Restore user registers and segments
-
- int_17h_exit:
- iret
-
- jmp_old_17h:
- jmp cs:dword ptr old_int_17h
-
-
- ; Routines for individual INT 17h services
-
- prt_char: ; Print a character:
- test prb_status[si],0FFh ; Stopped on DOS error?
- jnz prt_error ; Yes - don't buffer anything
- call BufChar ; No - put character in buffer
-
- prt_init: ; Initialize printer (no operation)
- prt_stat: ; Return printer status
- test prb_status[si],0FFh ; Stopped on DOS error?
- jnz prt_error ; Yes - return printer error
- test prb_user_flags[si],prbu_transp
- ; Transparent redirection?
- jnz use_old_17h ; Yes - chain to old handler
-
- mov stack_ah,90h ; Printer OK return code
- jmp int_17h_done
-
- prt_error:
- mov stack_ah,08h ; Printer I/O error code
- jmp int_17h_done
-
-
- install_flag: ; Return installed state flag
- mov stack_es,ds
- mov stack_ax,5A5Ah
- jmp int_17h_done
-
- set_redir: ; Redirect device
- call InTSR
- call SetRedir
- call OutTSR
- return_status: ; Use channel status for return code
- mov al,prb_status[si]
- mov stack_al,al
- jmp int_17h_done
-
- clear_redir: ; Terminate redirection
- call InTSR
- call ClearRedir
- call OutTSR
- jmp return_status
-
- flush_redir: ; Flush file to disk
- call InTSR
- call FlushRedir
- call OutTSR
- jmp return_status
-
- redir_stat: ; Return PRB pointer
- mov stack_bx,si
- mov stack_es,ds
- jmp int_17h_done
-
- redir_flags: ; Set user flag byte
- mov prb_user_flags[si],al
- jmp int_17h_done
-
- return_flags: ; Return user flag byte
- mov al,prb_user_flags[si]
- mov stack_al,al
- jmp int_17h_done
-
- Int_17h ENDP
-
- page
-
- ;
- ; Serial device service (INT 14h) interrupt handler
- ;
- ; This handler replaces the original INT 14h handler. Like
- ; the INT 17h handler, it handles redirection to files, but
- ; adds no new services (the INT 17h services work for both
- ; printer and comm port redirection).
- ;
- ; Entry: DX = comm port number (0 or 1)
- ; AH = 0 to initialize comm port
- ; AL = initialization parameters (see Tech. Ref.)
- ; AH = 1 to transmit a character
- ; AL = character to send
- ; AH = 2 to receive a character
- ; AH = 3 to return serial port status
- ;
- ; Exit: AH = comm port status:
- ; Bit 0 = data ready
- ; Bit 1 = overrun error
- ; Bit 2 = parity error
- ; Bit 3 = framing error
- ; Bit 4 = break detect
- ; Bit 5 = xmit holding register empty
- ; Bit 6 = xmit shift register empty
- ; Bit 7 = time out or general error
- ;
- ; AL = received character for service 2
- ; AL = line status for service 3 (see Tech. Ref.)
- ;
-
- Int_14h PROC near
-
- test cs:reentry_flag,0FFh ; Check for reentry
- jnz jmp_old_14h ; Reentry - can't do anything
- call InContext ; Use our stack and registers
- sti ; Allow hardware interrupts
-
- cmp dx,1 ; Comm port number 0 or 1?
- ja use_old_14h ; No - can't handle it
- mov bx,dx
- shl bx,1
- mov si,comm_prbs[bx] ; SI points to PRB
-
- test prb_sys_flags[si],prbs_active
- ; Redirection active for this port?
- jz use_old_14h ; No - let the old handler have it
-
- cmp ah,0
- jz comm_init ; Service 0
- cmp ah,1
- jz comm_out ; Service 1
- cmp ah,2
- jz comm_in ; Service 2
- cmp ah,3
- jz comm_stat ; Service 3
-
- use_old_14h:
- mov ax,stack_ax ; Registers changed by handler
- mov dx,stack_dx
- pushf ; Simulate and interrupt
- call dword ptr old_int_14h ; to the old handler
- mov stack_ax,ax ; Save return code
-
- int_14h_done:
- call OutContext ; Restore user registers and stack
-
- int_14h_exit:
- iret
-
- jmp_old_14h:
- jmp dword ptr cs:old_int_14h
-
-
- ; Routines to handle individual INT 14h services
-
- comm_out: ; Output a character
- test prb_status[si],0FFh ; Stopped on DOS error?
- jnz comm_error
- call BufChar ; No - buffer the character
-
- comm_init: ; Initialize comm port (no op)
- comm_stat: ; Return comm port status
- test prb_status[si],0FFh ; Stopped on error?
- jnz comm_error
- test prb_user_flags[si],prbu_transp
- ; Transparent redirection?
- jnz use_old_14h ; Yes - chain to old handler
- mov stack_ax,6030h ; Return "all OK" code
- jmp int_14h_done
-
- comm_in: ; Input from comm port
- test prb_user_flags[si],prbu_transp
- ; Transparent redirection...
- jnz use_old_14h ; ...uses old handler...
- ; ...else, return an error
-
- comm_error:
- mov stack_ax,8000h ; General-purpose serial error code
- jmp int_14h_done
-
- Int_14h ENDP
-
- page
-
- ;
- ; Set device redirection. Close any file already associated with
- ; this device, then copy the new filespec and user flags and open
- ; the file. If this is the first call to SetRedir (i.e., PRNDSK
- ; is just in the process of installing itself), the file will end
- ; up open but flagged as closed, and will actually be closed when
- ; the program exits (see FileOpen for details).
- ;
-
- SetRedir PROC near
-
- call ClearRedir ; Close old file (ignore return code)
-
- mov al,stack_al ; Copy user flags (transparent and
- mov prb_user_flags[si],al ; append mode bits)
-
- mov es,stack_ds
- mov bx,stack_si ; ES:BX points to filespec
- lea di,prb_filespec[si] ; DS:DI is destination for filespec
- mov cx,MAXSPEC-1 ; Maximum non-zero characters to copy
- copy_spec:
- mov al,byte ptr es:[bx] ; Move one character
- mov byte ptr [di],al
- inc bx ; Advance character pointers
- inc di
- test al,al ; Check for end of name
- loopnz copy_spec ; Repeat until end
- mov byte ptr [di],0 ; Just in case of truncation
-
- or prb_sys_flags[si],prbs_active
- ; Flag device as active
-
- call FileOpen ; Go open the file
-
- ret
-
- SetRedir ENDP
-
-
- ;
- ; Clear device redirection and close file. If the file is active
- ; (and not in an error state), its buffers are flushed. The file
- ; is then closed, regardless of its error state. This routine
- ; always returns zero in prb_status.
- ;
-
- ClearRedir PROC near
-
- test prb_sys_flags[si],prbs_active
- ; Is redirection active?
- jz clear_redir_1
- test prb_status[si],0FFh ; Skip flush on DOS error
- jnz clear_redir_1
-
- call Flush ; Flush the redirection buffer
-
- clear_redir_1:
- and prb_sys_flags[si],not prbs_active
- ; Device no longer active
-
- test prb_sys_flags[si],prbs_open
- ; Is the file open?
- jz clear_redir_2 ; Not open - can't close
-
- mov bx,prb_handle[si] ; BX = file handle
- mov ah,3Eh ; DOS service:
- int 21h ; Close file
- mov crit_flag,0h ; Ignore ALL errors
-
- and prb_sys_flags[si],not prbs_open
- ; File no longer open
-
- clear_redir_2:
- mov prb_status[si],0 ; Clear channel status (just in case)
- mov prb_buf_count[si],0 ; Make sure buffer is empty
- ret
-
- ClearRedir ENDP
-
-
- ;
- ; Flush buffer to disk. This does the ol' standard trick of
- ; duplicating the file handle then closing the duplicate. Note
- ; that an error trying to duplicate the handle does not cause
- ; an error status for the channel.
- ;
-
- FlushRedir PROC near
-
- test prb_sys_flags[si],prbs_active
- ; Redirection active?
- jz flush_redir_exit ; No - can't flush anything
- test prb_status[si],0FFh ; Redirection stopped by error?
- jnz flush_redir_exit ; Yes - can't flush
-
- call Flush ; Flush redirection buffer
-
- test prb_status[si],0FFh ; Error while flushing buffer?
- jnz flush_redir_exit ; Yes - quit while we're ahead
-
- mov bx,prb_handle[si] ; BX = handle to duplicate
- mov ah,45h ; DOS service:
- int 21h ; Duplicate file handle
- jc flush_redir_exit ; Error - just leave quietly
-
- mov bx,ax ; BX = new handle
- mov ah,3Eh ; DOS service:
- int 21h ; Close file
- jc flush_redir_error
- test crit_flag,0FFh ; Check for critical errors
- jnz flush_redir_crit
- jmp flush_redir_exit
-
- flush_redir_error:
- test crit_flag,0FFh
- jnz flush_redir_1
- flush_redir_crit:
- mov al,83 ; Fake DOS 3.xx critical error code
- flush_redir_1:
- mov prb_status[si],al
- flush_redir_exit:
- ret
-
- FlushRedir ENDP
-
-
- ;
- ; Reserved stack for interrupt handlers
- ;
-
- db 1EEh dup (?) ; Total of 200h bytes stack
-
- stack_es dw ?
- stack_ds dw ?
- stack_di dw ?
- stack_si dw ?
- stack_bp dw ?
- stack_dx dw ?
- stack_cx dw ?
- stack_bx dw ?
- stack_ax label word
- stack_al db ?
- stack_ah db ?
-
- stack_top equ $ ; Initial value of SP
-
-
- ;
- ; Device redirection buffers. The actual space for the buffers
- ; overlays the transient part of the program. Printer output
- ; (say, via PrtSc) between the time the INT 17h service 81h
- ; call occurs and the time the main program TSRs could cause
- ; some pretty interesting problems.
- ;
-
- lpt1_buf equ $
- lpt2_buf equ lpt1_buf+buffer_size
- lpt3_buf equ lpt2_buf+buffer_size
- com1_buf equ lpt3_buf+buffer_size
- com2_buf equ com1_buf+buffer_size
-
- resident_size equ (com2_buf+buffer_size)-Code
- ; Total size of resident section
-
- page
-
- ;
- ; Transient data section
- ;
-
- switch_char db ? ; Command line switch character
- device_number dw ? ; LPT1, 2, 3 = 0, 1, 2; COM1, 2 = 4, 5
- user_filespec db MAXSPEC dup (?) ; User-input filespec
- dos_filespec db MAXSPEC dup (?) ; DOS's translated filespec
-
- switch_byte db 0 ; Command line switches:
- sw_a equ 80h ; /A - append
- sw_c equ 40h ; /C - close
- sw_f equ 20h ; /F - flush
- sw_n equ 10h ; /N - not transparent
- sw_t equ 8h ; /T - transparent
- switch_names db "ACFNT " ; Switch characters (8 bytes)
-
- leave_resident db 0 ; Non-zero to leave via TSR
-
- prb_ptr dw ?,? ; Pointer to device redirection block
-
-
- device_names label byte
- db "LPT1",0 ; The first five names consist of
- db "LPT2",0 ; five bytes apiece, and are used
- db "LPT3",0 ; by DevStat as well as GetDevice
- db "COM1",0
- db "COM2",0
- db "PRN",0
- db "AUX",0
- db 0
- device_ids db 0,1,2,3,4,0,3 ; Translates PRN -> LPT1, AUX -> COM1
-
-
- bad_dos_msg db "Incompatible DOS version",0Dh,0Ah,0
- bad_dev_msg db "Unknown device name",0Dh,0Ah,0
- bad_switch_msg db "Unknown or illegal switch",0Dh,0Ah,0
- bad_cmd_msg db "Unrecognized command",0Dh,0Ah,0
- not_there_msg db "Redirection software not installed",0Dh,0Ah,0
- general_msg db "General-purpose error message",0Dh,0Ah,0
- five_spaces db " ",0
-
- gen_err_msg db "DOS error code 0x",0
- dos_messages label byte
- db "File not found",0Dh,0Ah,0 ; 2
- db "Path not found",0Dh,0Ah,0 ; 3
- db "Too many open files",0Dh,0Ah,0 ; 4
- db "Access denied",0Dh,0Ah,0 ; 5
- db "Invalid handle",0Dh,0Ah,0 ; 6
- db "Critical I/O error",0Dh,0Ah,0 ; 83
- dos_codes db 2,3,4,5,6,83 ; DOS codes for the error messages
- ; above
-
- page
-
- ;
- ; COM program entry point
- ;
-
- PrnDsk PROC near
-
- mov ah,30h ; DOS service:
- int 21h ; Get DOS version number
- mov dos_version,al ; Save version number
- test al,al ; DOS 1.x?
- jnz prndsk_1 ; No - breathe a sigh of relief
-
- mov bx,offset bad_dos_msg ; "Incompatible DOS version"
- call PutStr
- mov ah,0 ; DOS service:
- int 21h ; Exit program (DOS 1.x style)
-
- prndsk_1:
- mov ax,3700h ; DOS service:
- int 21h ; Get switch character
- mov switch_char,dl
-
- ; Get initial switches and device name from command line.
-
- mov si,offset command_line
- call SkipBl
- call GetSwitch
- jc bad_switch
-
- call GetDevice
- jnc prndsk_device ; Branch if device name found
-
- ; No device name found. Next character had better be a CR.
-
- test switch_byte,not (sw_c+sw_f)
- ; Only /C and /F allowed here
- jnz bad_switch
- call SkipBl
- cmp al,0Dh ; End of line?
- jnz bad_command ; No - something's wrong
-
- call IsThere ; Check that PRNDSK is installed
- jnz not_there
-
- call FormOne ; Go handle the command
- jmp prndsk_done
-
- ; Device name found. Look for more switches and "="
-
- prndsk_device:
- mov device_number,dx ; Save the device number
- call SkipBl
- call GetSwitch
- jc bad_switch
- call SkipBl
- cmp al,'='
- jz prndsk_spec ; Branch if "=" found
-
- test switch_byte,not (sw_c+sw_f+sw_n+sw_t)
- ; Allowed here: /C/F/N/T
- jnz bad_switch
- cmp al,0Dh ; End of line better be next
- jnz bad_command ; No - something's wrong
-
- call IsThere ; Can't do if PRNDSK not installed
- jnz not_there
-
- call FormTwo ; Go handle the command
- jmp prndsk_done
-
- ; "=" found after device name. Get file specification and
- ; any switches following it.
-
- prndsk_spec:
- inc si ; Skip the "="
- call SkipBl
- call GetSpec
- call GetSwitch
- jc bad_switch
-
- test switch_byte,not (sw_a+sw_t)
- ; Only /A and /T allowed
- jnz bad_switch
- call SkipBl
- cmp al,0Dh ; Followed by end of line?
- jnz bad_command
-
- call FormThree ; Handle the command
- jmp prndsk_done
-
- bad_switch: ; Unknown or illegal switch seen
- mov bx,offset bad_switch_msg
- jmp error_message
- bad_command: ; Bad device name or other error
- mov bx,offset bad_cmd_msg
- jmp error_message
- not_there: ; PRNDSK wasn't found in memory
- mov bx,offset not_there_msg
- jmp error_message
-
- error_message:
- call PutStr
-
- prndsk_done:
- test leave_resident,0FFh ; Have the vectors been installed?
- jnz prndsk_tsr ; Yes - leave PRNDSK in memory
- prndsk_exit:
- mov ax,4C00h ; DOS service:
- int 21h ; Exit with return code = 0
- prndsk_tsr:
- mov es,env_seg_ptr ; ES points to PRNDSK's environment
- mov ah,49h ; DOS service:
- int 21h ; Free allocated memory
- mov dx,(resident_size+15)/16
- ; DX = resident length (paragraphs)
- mov ax,3100h ; DOS service:
- int 21h ; Terminate and Keep Resident
-
- PrnDsk ENDP
-
-
- ;
- ; Handle commands of the form PRNDSK[/switches]
- ;
-
- FormOne PROC near
-
- test byte ptr switch_byte,0FFh
- jz list_all
- test byte ptr switch_byte,sw_c
- jnz close_all
- test byte ptr switch_byte,sw_f
- jnz flush_all
-
- ret
-
- list_all: ; PRNDSK <cr> - device status list
- mov dx,0 ; Start with device 0 (PRN)
- list_loop:
- push dx
- call DevStat ; Give status of device
- pop dx
- inc dx ; Next device
- cmp dx,5
- jb list_loop
- ret
-
- close_all: ; PRNDSK/C - close all devices
- mov dx,0
- close_loop:
- push dx
- mov ah,82h ; Extended printer service:
- int 17h ; Close and terminate
- pop dx
- inc dx
- cmp dx,5
- jb close_loop
- ret
-
- flush_all: ; PRNDSK/F - flush all channels
- mov dx,0
- flush_loop:
- push dx
- mov ah,83h ; Extended printer service:
- int 17h ; Flush to disk
- pop dx
- inc dx
- cmp dx,5
- jb flush_loop
- ret
-
- FormOne ENDP
-
-
- ;
- ; Handle commands of the form PRNDSK dev[/switches]
- ;
-
- FormTwo PROC near
-
- test byte ptr switch_byte,0FFh
- jz list_dev
- test byte ptr switch_byte,sw_c
- jnz close_dev
- test byte ptr switch_byte,sw_f
- jnz flush_dev
- form_2_1:
- test byte ptr switch_byte,sw_t
- jnz dev_transp
- test byte ptr switch_byte,sw_n
- jnz not_transp
-
- ret
-
- list_dev: ; PRNDSK device <cr>
- mov dx,device_number
- call DevStat
- ret
-
- close_dev: ; PRNDSK device/C
- mov dx,device_number
- mov ah,82h ; Extended printer service:
- int 17h ; Close and terminate redirection
- test al,al
- jnz form_2_error
- ret
-
- flush_dev: ; PRNDSK device/F
- mov dx,device_number
- mov ah,83h ; Extended printer service:
- int 17h ; Flush to disk
- test al,al
- jnz form_2_error
- jmp form_2_1
-
- dev_transp: ; PRNDSK device/T
- mov bl,prbu_transp
- dev_transp_1:
- mov dx,device_number
- mov ah,86h ; Extended printer service:
- int 17h ; Get user flag byte
- and al,not prbu_transp
- or al,bl
- mov ah,85h ; Extended printer service:
- int 17h ; Set user flag byte
- ret
-
- not_transp: ; PRNDSK device/N
- mov bl,0h
- jmp dev_transp_1
-
- form_2_error:
- call DOSError
- ret
-
- FormTwo ENDP
-
-
- ;
- ; Handle a command of the form PRNDSK dev=filespec[/switches]
- ;
-
- FormThree PROC near
-
- push ds ; Move DS...
- pop es ; ...to ES
- mov si,offset user_filespec ; DS:SI points to original filespec
- mov di,offset dos_filespec ; ES:DI points to translated filespec
- mov cx,MAXSPEC
- cmp dos_version,3 ; Service 60 doesn't exist
- jb form_3_dos_2 ; in DOS 2.xx
-
- mov ah,60h ; DOS service:
- int 21h ; Translate filespec
- jc form_3_error
- jmp form_3_install
-
- form_3_dos_2: ; Copy filespec, convert to upper case
- lodsb
- call Fold
- stosb
- test al,al
- loopnz form_3_dos_2
-
- form_3_install:
- call Install ; Make sure PRNDSK services installed
-
- ; Set the "append" and "transparent" bits in the user flag byte
-
- xor al,al
- test byte ptr switch_byte,sw_a
- jz form_3_1
- or al,prbu_append ; /A - set append flag
- form_3_1:
- test byte ptr switch_byte,sw_t
- jz form_3_2
- or al,prbu_transp ; /T - set transparent flag
- form_3_2:
-
- mov si,offset dos_filespec ; DS:SI points to filespec
- mov dx,device_number ; DX = device number
- mov ah,81h ; Extended printer service:
- int 17h ; Start device redirection
- test al,al
- jnz form_3_error
-
- ret
-
- form_3_error:
- call DOSError
- ret
-
- FormThree ENDP
-
- page
-
- ;
- ; Check whether PRNDSK is already installed
- ;
- ; Exit: ZF = true if PRNDSK is there
- ;
-
- IsThere PROC near
-
- mov ah,80h ; Extended printer service:
- int 17h ; Return PRNDSK installation flag
- cmp ax,5A5Ah
- jnz is_there_exit
-
- ; Installation flag ok; check program IDs to make sure
-
- mov si,offset program_id
- mov di,si
- mov cx,id_length
- repz cmpsb
-
- is_there_exit:
- ret
-
- IsThere ENDP
-
-
- ;
- ; Make sure PRNDSK is installed - install it if necessary
- ;
-
- Install PROC near
-
- call IsThere ; Already installed?
- jz install_exit ; Yes - we're done
-
- mov ah,51h ; DOS service:
- int 21h ; Get Process ID
- mov tsr_pid,bx ; Save TSR's PID
-
- mov ax,3521h ; DOS service:
- int 21h ; Get interrupt vector 21h
- mov old_int_21h+0,bx ; Store old INT 21h vector
- mov old_int_21h+2,es
-
- mov dx,offset Int_21h ; Point to our INT 21h handler
- mov ax,2521h ; DOS service:
- int 21h ; Set interrupt vector 21h
-
- mov ax,3517h ; DOS service:
- int 21h ; Get interrupt vector 17h
- mov old_int_17h+0,bx ; Store old printer services vector
- mov old_int_17h+2,es
-
- mov dx,offset Int_17h ; Point to our INT 17h handler
- mov ax,2517h ; DOS service:
- int 21h ; Set interrupt vector 17h
-
- mov ax,3514h ; DOS service:
- int 21h ; Get interrupt vector 14h
- mov old_int_14h+0,bx ; Store old serial services vector
- mov old_int_14h+2,es
-
- mov dx,offset Int_14h ; Point to our INT 14h handler
- mov ax,2514h ; DOS service:
- int 21h ; Set interrupt vector 14h
-
- mov leave_resident,0FFh ; Indicate handlers to be left resident
-
- install_exit:
- ret
-
- Install ENDP
-
-
- ;
- ; Skip blanks on command line
- ;
- ; Exit: AL = next non-blank character
- ;
-
- SkipBl PROC near
- skipbl_loop:
- mov al,[si]
- cmp al,' '
- jnz skipbl_exit
- inc si
- jmp skipbl_loop
- skipbl_exit:
- ret
- SkipBl ENDP
-
-
- ;
- ; Fold a lower-case character to upper-case
- ;
- ; Entry: AL = character
- ;
-
- Fold PROC near
- cmp al,'a'
- jb fold_exit
- cmp al,'z'
- ja fold_exit
- sub al,'a'-'A'
- fold_exit:
- ret
- Fold ENDP
-
-
- ;
- ; Get a list of switches
- ;
- ; Entry: SI points to next character on command line
- ;
- ; Exit: Switch bits have been added to switch_byte
- ;
-
- GetSwitch PROC near
-
- next_switch:
- call SkipBl
- cmp al,switch_char ; Another switch?
- jnz get_switch_exit
-
- inc si ; Point to next character
- mov al,[si] ; Get switch identifier
- call Fold
-
- mov dh,80h ; Bit 7 for first switch in list
- mov bx,0 ; Index into switch name table
- switch_loop:
- cmp al,switch_names[bx] ; Do the names match?
- jz got_switch
- inc bx ; No - next switch name
- shr dh,1 ; and next switch bit
- jnc switch_loop
- ret ; With carry set
-
- got_switch:
- or switch_byte,dh ; Set switch bit
- inc si ; Swallow character from command line
- jmp next_switch
-
- get_switch_exit:
- clc
- ret
-
- GetSwitch ENDP
-
-
- ;
- ; Get a device name from the command line
- ;
-
- GetDevice PROC near
-
- mov di,offset device_names ; DI points into device name table
- xor dx,dx ; DX is entry number in table
-
- get_dev_1:
- test byte ptr [di],0FFh ; Found end of table?
- jz no_device
- xor bx,bx ; BX is offset into device name
-
- get_dev_2:
- test byte ptr [di+bx],0FFh ; Found end of name in table?
- jz got_device
-
- mov al,[si+bx] ; Get next character from command line
- call Fold ; Convert to upper case
- cmp al,[di+bx] ; Does it match the device name?
- jnz skip_device ; No - skip this name
-
- inc bx ; Advance to next character
- jmp get_dev_2
-
- skip_device: ; This device doesn't match - get next
- mov al,[di]
- inc di ; Advance device name table pointer
- test al,al ; until a zero is seen
- jnz skip_device
- inc dx ; Increment device number
- jmp get_dev_1
-
- no_device: ; No match for name - return error
- stc
- ret
-
- got_device:
- add si,bx ; Advance command line pointer
- cmp byte ptr [si],':' ; Check for ":" after name
- jnz got_dev_1
- inc si ; Eat ":"
-
- got_dev_1:
- mov bx,dx
- mov dl,device_ids[bx] ; This maps PRN to LPT1, AUX to COM1
- clc
- ret
-
- GetDevice ENDP
-
-
- ;
- ; Get a filespec from the command line. Characters are copied
- ; until finding a blank, end of line, or the switch character.
- ;
- ; Entry: SI points to command line (current position)
- ;
- ; Exit: SI points to first character after filespec
- ;
-
- GetSpec PROC near
-
- mov di,offset user_filespec ; Destination pointer
- mov cx,MAXSPEC-1 ; Maximum characters to copy
-
- spec_loop:
- mov al,[si] ; Get next character
- cmp al,0Dh ; Check for EOL...
- jz got_spec
- cmp al,' ' ; ...or blank...
- jz got_spec
- cmp al,switch_char ; ...or switch character
- jz got_spec
-
- mov [di],al ; Store character in buffer
- inc si ; Advance pointers
- inc di
- loop spec_loop
-
- got_spec:
- mov byte ptr [di],0 ; Just in case CX ran out
- ret
-
- GetSpec ENDP
-
-
- ;
- ; Print the status of a redirectable device. The device name
- ; and filespec are printed, along with the transparency flag
- ; and (if appropriate) error message.
- ;
- ; Entry: DX = device number (0 to 4)
- ;
-
- DevStat PROC near
-
- mov ah,84h ; Extended printer service:
- int 17h ; Get PRB pointer
- test es:prb_sys_flags[bx],prbs_active
- ; Redirection active for this device?
- jz dev_stat_2
-
- mov prb_ptr+0,bx ; Save PRB pointer
- mov prb_ptr+2,es
-
- mov bx,dx ; BX = DX * 5 (offset into device
- shl bx,1 ; names table)
- shl bx,1
- add bx,dx
- add bx,offset device_names ; BX points to device name
- call PutStr
-
- mov dl,'='
- call PutChar
-
- push ds
- lds bx,dword ptr cs:prb_ptr ; DS:BX points to PRB...
- add bx,prb_filespec ; ...now points to filespec
- call PutStr
- pop ds
-
- les bx,dword ptr cs:prb_ptr
- test es:prb_user_flags[bx],prbu_transp
- ; Check transparency flag
- jz dev_stat_1
-
- mov dl,switch_char ; Print a "/" (or something like it)
- call PutChar
- mov dl,'T' ; Print a "T"
- call PutChar
-
- dev_stat_1:
- mov dl,0Dh
- call PutChar
- mov dl,0Ah
- call PutChar
-
- mov al,es:prb_status[bx] ; Get device status byte
- test al,al
- jz dev_stat_2 ; Branch if no error
-
- push ax
- mov bx,offset five_spaces
- call PutStr
-
- pop ax
- call DOSError
-
- dev_stat_2:
- ret
-
- DevStat ENDP
-
-
- ;
- ; Print a DOS error message
- ;
- ; Entry: AL = error code
- ;
-
- DOSError PROC near
-
- ; First, search the dos_codes table to see if there is a
- ; specific error message for this code.
-
- xor bx,bx
- find_message:
- test dos_codes[bx],0FFh ; End of table?
- jz no_message
- cmp al,dos_codes[bx] ; Found a match for error code?
- jz got_msg_number
- inc bx ; Next table entry
- jmp find_message
-
- ; No special message, use the general-purpose one.
-
- no_message:
- push ax
- mov bx,offset gen_err_msg ; "DOS error code 0x"
- call PutStr
- pop ax
- call PutHex ; Print error number
- mov dl,0Dh ; Print <CR>...
- call PutChar
- mov dl,0Ah ; ...<LF>
- call PutChar
- ret
-
- ; BX contains the message number. Scan through the message
- ; table to find the string represented by that number.
-
- got_msg_number:
- mov cx,bx ; CX counts down from message number
- mov bx,offset dos_messages ; Point to start of messages
- jcxz got_msg_ptr ; Branch for message 0
- find_msg_ptr:
- mov al,[bx] ; Get message character
- inc bx ; and advance pointer
- test al,al ; Found end of a string?
- jnz find_msg_ptr ; No - continue scanning
- loop find_msg_ptr ; Loop till correct number passed
-
- got_msg_ptr:
- call PutStr
- ret
-
- DOSError ENDP
-
-
- ;
- ; Print a zero-terminated string
- ;
- ; Entry: BX points to string
- ;
-
- PutStr PROC near
-
- put_str_loop:
- mov dl,[bx]
- test dl,dl
- jz put_str_exit
- call PutChar
- inc bx
- jmp put_str_loop
-
- put_str_exit:
- ret
-
- PutStr ENDP
-
-
- ;
- ; Print a character
- ;
- ; Entry: DL = character
- ;
-
- PutChar PROC near
- mov ah,2h
- int 21h
- ret
- PutChar ENDP
-
-
- ;
- ; Print two hex digits
- ;
- ; Entry: AL = hex byte to print
- ;
-
- PutHex PROC near
-
- push ax
- shr al,1
- shr al,1
- shr al,1
- shr al,1
- call put_hex_digit
- pop ax
- and al,0Fh
-
- put_hex_digit:
- add al,90h
- daa
- adc al,40h
- daa
- mov dl,al
- call PutChar
- ret
-
- PutHex ENDP
-
-
- Code ENDS
- END Start
-